Ylitä perustason tyypitykset. Hallitse TypeScriptin edistyneitä ominaisuuksia, kuten ehdollisia tyyppejä, mallinekirjaimia ja merkkijonojen käsittelyä rakentaaksesi erittäin vankkoja ja tyyppiturvallisia API:ja. Kattava opas globaaleille kehittäjille.
TypeScriptin täyden potentiaalin vapauttaminen: Syvä sukellus ehdollisiin tyyppeihin, mallinekirjaimiin ja edistyneeseen merkkijonojen käsittelyyn
Nykyaikaisen ohjelmistokehityksen maailmassa TypeScript on kehittynyt kauas alkuperäisestä roolistaan yksinkertaisena JavaScriptin tyyppitarkistajana. Siitä on tullut hienostunut työkalu sille, mitä voidaan kuvailla tyyppitason ohjelmoinniksi. Tämä paradigma antaa kehittäjille mahdollisuuden kirjoittaa koodia, joka toimii itse tyypeissä, luoden dynaamisia, itse dokumentoivia ja huomattavan turvallisia API:ja. Tämän vallankumouksen ytimessä on kolme tehokasta ominaisuutta, jotka toimivat yhdessä: Ehdolliset tyypit, mallinekirjaintyypit ja joukko sisäänrakennettuja merkkijonojen käsittelytyyppejä.
Kehittäjille ympäri maailmaa, jotka haluavat nostaa TypeScript-taitojaan, näiden käsitteiden ymmärtäminen ei ole enää ylellisyyttä – se on välttämättömyys skaalautuvien ja ylläpidettävien sovellusten rakentamiseksi. Tämä opas vie sinut syvälle sukellukseen, alkaen perusperiaatteista ja rakentaen monimutkaisiin, todellisiin malleihin, jotka osoittavat niiden yhdistettyä voimaa. Olitpa rakentamassa suunnittelujärjestelmää, tyyppiturvallista API-asiakasta tai monimutkaista datankäsittelykirjastoa, näiden ominaisuuksien hallitseminen muuttaa perusteellisesti tapaa, jolla kirjoitat TypeScriptiä.
Perusta: Ehdolliset tyypit (`extends`-kolmitoiminen operaattori)
Ytimeltään ehdollinen tyyppi antaa sinun valita yhden kahdesta mahdollisesta tyypistä tyyppisuhteen tarkistuksen perusteella. Jos olet perehtynyt JavaScriptin kolmitoimiseen operaattoriin (condition ? valueIfTrue : valueIfFalse), syntaksi on heti intuitiivinen:
type Result = SomeType extends OtherType ? TrueType : FalseType;
Tässä extends-avainsana toimii ehtona. Se tarkistaa, onko SomeType määritettävissä tyyppiin OtherType. Puretaan se yksinkertaisella esimerkillä.
Perusesimerkki: Tyypin tarkistaminen
Kuvittele, että haluamme luoda tyypin, joka ratkeaa arvoon true, jos annettu tyyppi T on merkkijono, ja muuten arvoon false.
type IsString
Voimme sitten käyttää tätä tyyppiä näin:
type A = IsString<"hello">; // tyyppi A on true
type B = IsString<123>; // tyyppi B on false
Tämä on perusrakennuspalikka. Mutta ehdollisten tyyppien todellinen voima vapautuu, kun se yhdistetään infer-avainsanan kanssa.
`infer`-avainsanan voima: Tyypien poimiminen sisältä
infer-avainsana on pelin muuttaja. Sen avulla voit ilmoittaa uuden geneerisen tyyppimuuttujan sisällä extends-lausekkeessa, ja se kaappaa tehokkaasti osan tarkistettavasta tyypistä. Ajattele sitä tyyppitason muuttujailmoituksena, joka saa arvonsa mallin täsmäytyksestä.
Klassinen esimerkki on Promise-sisällä olevan tyypin purkaminen.
type UnwrapPromise
Analysoidaan tämä:
T extends Promise: Tämä tarkistaa, onkoTPromise. Jos on, TypeScript yrittää täsmätä rakenteen.infer U: Jos täsmäytys onnistuu, TypeScript kaappaa tyypin, johonPromiseratkeaa, ja sijoittaa sen uuteen tyyppimuuttujaan nimeltäU.? U : T: Jos ehto on tosi (ToliPromise), tuloksena oleva tyyppi onU(purettu tyyppi). Muussa tapauksessa tuloksena oleva tyyppi on vain alkuperäinen tyyppiT.
Käyttö:
type User = { id: number; name: string; };
type UserPromise = Promise
type UnwrappedUser = UnwrapPromise
type UnwrappedNumber = UnwrapPromise
Tämä malli on niin yleinen, että TypeScript sisältää sisäänrakennettuja aputyökaluja, kuten ReturnType, joka on toteutettu samalla periaatteella funktion palautustyypin poimimiseksi.
Distributiiviset ehdolliset tyypit: Unionien kanssa työskentely
Ehdollisten tyyppien kiehtova ja ratkaiseva käyttäytyminen on, että niistä tulee distributiivisia, kun tarkistettava tyyppi on "paljas" geneerinen tyyppiparametri. Tämä tarkoittaa, että jos välität sille unionityypin, ehtoa sovelletaan unionin jokaiseen jäseneen erikseen, ja tulokset kerätään takaisin uuteen unioniin.
Harkitse tyyppiä, joka muuntaa tyypin kyseisen tyypin taulukoksi:
type ToArray
Jos välitämme unionityypin arvoon ToArray:
type StrOrNumArray = ToArray
Tulos ei ole (string | number)[]. Koska T on paljas tyyppiparametri, ehto jaetaan:
ToArraymuuttuu muotoonstring[]ToArraymuuttuu muotoonnumber[]
Lopullinen tulos on näiden yksittäisten tulosten unioni: string[] | number[].
Tämä distributiivinen ominaisuus on uskomattoman hyödyllinen unionien suodattamiseen. Esimerkiksi sisäänrakennettu Extract -aputyökalu käyttää tätä valitakseen unionista T jäsenet, jotka ovat määritettävissä tyyppiin U.
Jos sinun on estettävä tämä distributiivinen käyttäytyminen, voit kääriä tyyppiparametrin tupleen extends-lausekkeen molemmin puolin:
type ToArrayNonDistributive
type StrOrNumArrayUnified = ToArrayNonDistributive
Tällä vankalla pohjalla tutkitaan, kuinka voimme luoda dynaamisia merkkijonotyyppejä.
Dynaamisten merkkijonojen rakentaminen tyyppitasolla: Mallinekirjaintyypit
TypeScript 4.1:ssä esitellyt mallinekirjaintyypit antavat sinun määrittää tyyppejä, jotka ovat JavaScriptin mallinekirjainmerkkijonojen muotoisia. Niiden avulla voit ketjuttaa, yhdistää ja luoda uusia merkkijonokirjaintyyppejä olemassa olevista.
Syntaksi on juuri sitä, mitä odotat:
type World = "World";
type Greeting = `Hello, ${World}!`; // tyyppi Greeting on "Hello, World!"
Tämä saattaa vaikuttaa yksinkertaiselta, mutta sen voima piilee sen yhdistämisessä unioneihin ja geneerisiin tyyppeihin.
Unioneita ja permutaatioita
Kun mallinekirjaintyyppi sisältää unionin, se laajenee uuteen unioniin, joka sisältää kaikki mahdolliset merkkijonopermutaatiot. Tämä on tehokas tapa luoda joukko hyvin määriteltyjä vakioita.
Kuvittele, että määrittelet joukon CSS-marginaaliominaisuuksia:
type Side = "top" | "right" | "bottom" | "left";
type MarginProperty = `margin-${Side}`;
Tuloksena oleva tyyppi MarginProperty-tyypille on:
"margin-top" | "margin-right" | "margin-bottom" | "margin-left"
Tämä on täydellinen luomaan tyyppiturvallisia komponenttien ominaisuuksia tai funktion argumentteja, joissa sallitaan vain tietyt merkkijonamuodot.
Yhdistäminen geneerisiin tyyppeihin
Mallinekirjaimet loistavat todella, kun niitä käytetään geneeristen tyyppien kanssa. Voit luoda tehdas tyyppejä, jotka luovat uusia merkkijonokirjaintyyppejä jonkin syötteen perusteella.
type MakeEventListener
type UserListener = MakeEventListener<"user">; // "onUserChange"
type ProductListener = MakeEventListener<"product">; // "onProductChange"
Tämä malli on avain dynaamisten, tyyppiturvallisten API:ien luomiseen. Mutta entä jos meidän on muutettava merkkijonon kirjainkokoa, kuten muuttamalla "user" arvoon "User" saadaksemme "onUserChange"? Siinä merkkijonojen käsittelytyypit tulevat kuvaan.
Työkalupakki: Sisäänrakennetut merkkijonojen käsittelytyypit
Jotta mallinekirjaimista tulisi vieläkin tehokkaampia, TypeScript tarjoaa joukon sisäänrakennettuja tyyppejä merkkijonokirjainten käsittelyyn. Nämä ovat kuin apufunktioita, mutta tyyppijärjestelmälle.
Kirjainkoon muokkaajat: `Uppercase`, `Lowercase`, `Capitalize`, `Uncapitalize`
Nämä neljä tyyppiä tekevät juuri sen, mitä niiden nimet viittaavat:
Uppercase: Muuntaa koko merkkijonotyypin isoiksi kirjaimiksi.type LOUD = Uppercase<"hello">; // "HELLO"Lowercase: Muuntaa koko merkkijonotyypin pieniksi kirjaimiksi.type quiet = Lowercase<"WORLD">; // "world"Capitalize: Muuntaa merkkijonotyypin ensimmäisen merkin isoksi kirjaimeksi.type Proper = Capitalize<"john">; // "John"Uncapitalize: Muuntaa merkkijonotyypin ensimmäisen merkin pieneksi kirjaimeksi.type variable = Uncapitalize<"PersonName">; // "personName"
Palataan edelliseen esimerkkiimme ja parannetaan sitä käyttämällä Capitalize-tyyppiä luomaan perinteisiä tapahtumankäsittelijöiden nimiä:
type MakeEventListener
type UserListener = MakeEventListener<"user">; // "onUserChange"
type ProductListener = MakeEventListener<"product">; // "onProductChange"
Nyt meillä on kaikki osat. Katsotaan, kuinka ne yhdistyvät ratkaisemaan monimutkaisia, todellisia ongelmia.
Synteesi: Kaikkien kolmen yhdistäminen edistyneitä malleja varten
Tässä teoria kohtaa käytännön. Yhdistämällä ehdollisia tyyppejä, mallinekirjaimia ja merkkijonojen käsittelyä voimme rakentaa uskomattoman hienostuneita ja turvallisia tyyppimäärityksiä.
Malli 1: Täysin tyyppiturvallinen tapahtumalähetin
Tavoite: Luo geneerinen EventEmitter-luokka, jossa on menetelmiä, kuten on(), off() ja emit(), jotka ovat täysin tyyppiturvallisia. Tämä tarkoittaa:
- Menetelmille välitetyn tapahtuman nimen on oltava kelvollinen tapahtuma.
emit()-metodille välitetyn hyötykuorman on vastattava kyseiselle tapahtumalle määritettyä tyyppiä.on()-metodille välitettävän takaisinkutsufunktion on hyväksyttävä oikea hyötykuormatyyppi kyseiselle tapahtumalle.
Määritetään ensin tapahtumien nimien kartta niiden hyötykuormatyypeille:
interface EventMap {
"user:created": { userId: number; name: string; };
"user:deleted": { userId: number; };
"product:added": { productId: string; price: number; };
}
Nyt voimme rakentaa geneerisen EventEmitter-luokan. Käytämme geneeristä parametria Events, jonka on laajennettava EventMap-rakennettamme.
class TypedEventEmitter
private listeners: { [K in keyof Events]?: ((payload: Events[K]) => void)[] } = {};
// `on`-metodi käyttää geneeristä `K`-tyyppiä, joka on avain Events-kartassamme
on
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event]?.push(callback);
}
// `emit`-metodi varmistaa, että hyötykuorma vastaa tapahtuman tyyppiä
emit
this.listeners[event]?.forEach(callback => callback(payload));
}
}
Luodaan ja käytetään sitä:
const appEvents = new TypedEventEmitter
// Tämä on tyyppiturvallinen. Hyötykuorma päätellään oikein arvoksi { userId: number; name: string; }
appEvents.on("user:created", (payload) => {
console.log(`User created: ${payload.name} (ID: ${payload.userId})`);
});
// TypeScript antaa virheen tässä, koska "user:updated" ei ole avain EventMap-tyypissä
// appEvents.on("user:updated", () => {}); // Virhe!
// TypeScript antaa virheen tässä, koska hyötykuormasta puuttuu "name"-ominaisuus
// appEvents.emit("user:created", { userId: 123 }); // Virhe!
Tämä malli tarjoaa käännösaikaisen turvallisuuden sille, mikä on perinteisesti erittäin dynaaminen ja virhealtis osa monia sovelluksia.
Malli 2: Tyyppiturvallinen polun käyttö sisäkkäisiin objekteihin
Tavoite: Luo aputyökalutyyppi, PathValue, joka voi määrittää arvon tyypin sisäkkäisessä objektissa T käyttämällä pisteennotaation merkkijonopolkua P (esim. "user.address.city").
Tämä on erittäin edistynyt malli, joka esittelee rekursiivisia ehdollisia tyyppejä.
Tässä on toteutus, jonka jaamme osiin:
type PathValue
? Key extends keyof T
? PathValue
: never
: P extends keyof T
? T[P]
: never;
Jäljitetään sen logiikkaa esimerkin avulla: PathValue
- Alkuperäinen kutsu:
Pon"a.b.c". Tämä vastaa mallinekirjainta`${infer Key}.${infer Rest}`. Keypäätellään arvoksi"a".Restpäätellään arvoksi"b.c".- Ensimmäinen rekursio: Tyyppi tarkistaa, onko
"a"avainMyObject-objektissa. Jos on, se kutsuu rekursiivisestiPathValue. - Toinen rekursio: Nyt
Pon"b.c". Se vastaa mallinekirjainta uudelleen. Keypäätellään arvoksi"b".Restpäätellään arvoksi"c".- Tyyppi tarkistaa, onko
"b"avainMyObject["a"]-objektissa, ja kutsuu rekursiivisestiPathValue. - Perustapaus: Lopuksi
Pon"c". Tämä ei vastaa muotoa`${infer Key}.${infer Rest}`. Tyyppilogiikka putoaa toiseen ehtoon:P extends keyof T ? T[P] : never. - Tyyppi tarkistaa, onko
"c"avainMyObject["a"]["b"]-objektissa. Jos on, tulos onMyObject["a"]["b"]["c"]. Jos ei, se onnever.
Käyttö apufunktion kanssa:
declare function get
const myObject = {
user: {
name: "Alice",
address: {
city: "Wonderland",
zip: 12345
}
}
};
const city = get(myObject, "user.address.city"); // const city: string
const zip = get(myObject, "user.address.zip"); // const zip: number
const invalid = get(myObject, "user.email"); // const invalid: never
Tämä tehokas tyyppi estää ajonaikaiset virheet poluissa olevista kirjoitusvirheistä ja tarjoaa täydellisen tyypin päättelyn syvästi sisäkkäisille tietorakenteille, mikä on yleinen haaste globaaleissa sovelluksissa, jotka käsittelevät monimutkaisia API-vastauksia.
Parhaat käytännöt ja suorituskyvynäkökohdat
Kuten minkä tahansa tehokkaan työkalun kohdalla, on tärkeää käyttää näitä ominaisuuksia viisaasti.
- Priorisoi luettavuus: Monimutkaisista tyypeistä voi tulla nopeasti lukukelvottomia. Jaa ne pienempiin, hyvin nimettyihin apurityyppeihin. Käytä kommentteja selittämään logiikkaa, aivan kuten tekisit monimutkaisessa ajonaikaisessa koodissa.
- Ymmärrä
never-tyyppi:never-tyyppi on ensisijainen työkalusi virhetilojen käsittelyyn ja unionien suodattamiseen ehdollisissa tyypeissä. Se edustaa tilaa, jota ei pitäisi koskaan esiintyä. - Varo rekursiorajoituksia: TypeScriptillä on rekursiosyvyysraja tyypin ilmentämiselle. Jos tyyppisi ovat liian syvästi sisäkkäisiä tai äärettömän rekursiivisia, kääntäjä antaa virheen. Varmista, että rekursiivisilla tyypeilläsi on selkeä perustapaus.
- Valvo IDE:n suorituskykyä: Erittäin monimutkaiset tyypit voivat joskus vaikuttaa TypeScript-kielipalvelimen suorituskykyyn, mikä johtaa hitaampaan automaattiseen täydennykseen ja tyypin tarkistukseen editorissasi. Jos koet hidastuksia, katso, voidaanko monimutkaista tyyppiä yksinkertaistaa tai jakaa osiin.
- Tiedä milloin lopettaa: Nämä ominaisuudet on tarkoitettu ratkaisemaan tyyppiturvallisuuden ja kehittäjäkokemuksen monimutkaisia ongelmia. Älä käytä niitä yksinkertaisten tyyppien yliteknistämiseen. Tavoitteena on parantaa selkeyttä ja turvallisuutta, ei lisätä tarpeetonta monimutkaisuutta.
Johtopäätös
Ehdolliset tyypit, mallinekirjaimet ja merkkijonojen käsittelytyypit eivät ole vain erillisiä ominaisuuksia; ne ovat tiiviisti integroitu järjestelmä hienostuneen logiikan suorittamiseen tyyppitasolla. Ne antavat meille mahdollisuuden siirtyä yksinkertaisia annotaatioita pidemmälle ja rakentaa järjestelmiä, jotka ovat syvästi tietoisia omasta rakenteestaan ja rajoituksistaan.
Hallitsemalla tämän trion voit:
- Luo itse dokumentoivia API:ja: Itse tyypeistä tulee dokumentaatio, joka ohjaa kehittäjiä käyttämään niitä oikein.
- Poista kokonaisia vikaluokkia: Tyyppivirheet havaitaan käännösaikana, ei käyttäjien toimesta tuotannossa.
- Paranna kehittäjäkokemusta: Nauti rikkaasta automaattisesta täydennyksestä ja tekstin sisäisistä virheviesteistä jopa koodisi dynaamisimmille osille.
Näiden edistyneiden ominaisuuksien omaksuminen muuttaa TypeScriptin turvaverkosta tehokkaaksi kumppaniksi kehityksessä. Sen avulla voit koodata monimutkaista liiketoimintalogiikkaa ja invariantteja suoraan tyyppijärjestelmään varmistaen, että sovelluksesi ovat vankempia, ylläpidettävämpiä ja skaalautuvampia globaalille yleisölle.